package com.wang.transaction.product.controller;


import com.wang.transaction.product.domain.ProductInfo;
import com.wang.transaction.product.service.MPProductInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <p>
 * 商品 前端控制器
 * </p>
 * 注意：测试嵌套事务必须通过springBean调用，不能直接用内部方法。比如Aservice.a()调用b()，必须要注入Aservice aService，用aService.b()
 *
 * @author wly
 * @since 2018-07-16
 */
@RestController
@RequestMapping("/productInfo")
public class ProductInfoController {

    @Autowired
    private MPProductInfoService productInfoService;

    /**
     * 测试
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED)
    @RequestMapping(value = "/buy")
    public String buy() throws Exception {
        System.out.println("in=====================" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        // 查询
        ProductInfo selectProduct = productInfoService.selectById(1L);
        System.out.println("selectProduct：" + selectProduct);
        // 修改
        ProductInfo productInfo = new ProductInfo();
        productInfo.setId(1L);
        productInfo.setAmount(selectProduct.getAmount() - 1);
        productInfoService.updateById(productInfo);
        System.out.println("do update");
        // 休眠2s
        Thread.sleep(3000L);
        System.out.println("do sleep");
        // 查询
        ProductInfo selectProduct2 = productInfoService.selectById(1L);
        System.out.println(("selectProduct2：" + selectProduct2 + new SimpleDateFormat("HH:mm:ss").format(new Date())));
        return "done";
    }

    /**
     * 读未提交，能读到其他事务未提交的数据
     * 读取到其它事务未提交的数据，若其它事务异常则数据无效，再读一次数据就变了（脏读、不可重复读、幻读）
     * 线程   A       B
     * 读    1000
     * 写    -100
     * 900
     * xx异常
     * 事务回滚
     * 写           -100
     * 提交事务
     * 结果：800
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED)
    @RequestMapping(value = "/buy2")
    public String buy2() throws Exception {
        // 查询
        ProductInfo selectProduct = productInfoService.selectById(1L);
        System.out.println("2.读未提交===selectProduct：" + selectProduct);
        // 修改
        ProductInfo productInfo = new ProductInfo();
        productInfo.setId(1L);
        productInfo.setAmount(selectProduct.getAmount() - 1);
        productInfoService.updateById(productInfo);
        System.out.println("2.读未提交===do update");
        // 休眠2s
        Thread.sleep(3000L);
        System.out.println("2.读未提交===do sleep");
        // 查询
        ProductInfo selectProduct2 = productInfoService.selectById(1L);
        System.out.println("2.读未提交===selectProduct2：" + selectProduct2);
        return "done";
    }

    /**
     * 读已提交（Oracle默认），只有其他事务提交才能被读取到
     * 问题：先读取，在修改，并发场景下，先修改的记录丢失（幻读、不可重复读）
     * 线程   A       B
     * 读    1000
     * 操作  -100
     * 1000
     * 提交事务
     * -100
     * 提交事务
     * 结果：900
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
    @RequestMapping(value = "/buy3")
    public String buy3() throws Exception {
        // 查询
        ProductInfo selectProduct = productInfoService.selectById(1L);
        System.out.println("3.读已提交===selectProduct：" + selectProduct);
        // 修改
        ProductInfo productInfo = new ProductInfo();
        productInfo.setId(1L);
        productInfo.setAmount(selectProduct.getAmount() - 1);
        productInfoService.updateById(productInfo);
        System.out.println("3.读已提交===do update");
        // 休眠2s
        Thread.sleep(3000L);
        System.out.println("3.读已提交===do sleep");
        // 查询
        ProductInfo selectProduct2 = productInfoService.selectById(1L);
        System.out.println("3.读已提交===selectProduct2：" + selectProduct2);
        return "done";
    }

    /**
     * 可重复读（mysql默认），只能读取到其他事务已提交的数据，可以重复读取
     * 可以重复读取，但是可能读取到已经修改过的数据（幻读）
     * 线程   A       B
     * 读   1000
     * 写   -100
     * 读           1000
     * 提交事务
     * 读           1000
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ)
    @RequestMapping(value = "/buy4")
    public String buy4() throws Exception {
        // 查询
        ProductInfo selectProduct = productInfoService.selectById(1L);
        System.out.println("4.可重复读取===selectProduct：" + selectProduct);
        // 修改
        ProductInfo productInfo = new ProductInfo();
        productInfo.setId(1L);
        productInfo.setAmount(selectProduct.getAmount() - 2);
        productInfoService.updateById(productInfo);
        // 休眠2s
        Thread.sleep(3000L);
        System.out.println("4.可重复读取===do sleep");
        // 查询
        ProductInfo selectProduct2 = productInfoService.selectById(1L);
        System.out.println("4.可重复读取===selectProduct2：" + selectProduct2);
        return "done";
    }

    /**
     * 购买商品5，串行操作
     * 经测试即使web是集群，sql也是串行执行
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE)
    @RequestMapping(value = "/buy5")
    public String buy5() throws Exception {
        System.out.println("5.串行操作===in" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        // 查询
        ProductInfo selectProduct = productInfoService.selectById(1L);
        System.out.println(("5.串行操作===selectProduct：" + selectProduct + new SimpleDateFormat("HH:mm:ss").format(new Date())));
        // 修改
        ProductInfo productInfo = new ProductInfo();
        productInfo.setId(1L);
        productInfo.setAmount(selectProduct.getAmount() - 1);
        productInfoService.updateById(productInfo);
        System.out.println("5.串行操作===do update");
        // 休眠2s
        Thread.sleep(3000L);
        System.out.println("5.串行操作===do sleep");
        // 查询
        ProductInfo selectProduct2 = productInfoService.selectById(1L);
        System.out.println("5.串行操作===selectProduct2：" + selectProduct2);
        return "done";
    }

    /**
     * 嵌套事务测试（Required）
     * Required：没有事务就开启一个事务，有事务就加入当前事务中
     * 内层或外层事务回滚，整个事务都会回滚
     */
    @RequestMapping("/nestRequired")
    public String nestRequired() throws Exception {
        productInfoService.serviceA();
        return "done";
    }

    /**
     * 嵌套事务测试（Required_NEW）
     * Required_NEW：挂起当前事务，开启一个新事务，内层、外层事务是两个独立的事务，不会相互影响
     * 内层事务回滚不影响外层事务
     * 外层回滚不影响内层事务
     */
    @RequestMapping("/nestRequiredNew")
    public String nestRequiredNew() throws Exception {
        productInfoService.serviceC();
        return "done";
    }

    /**
     * 嵌套事务测试（Nest），事务的提交、回滚是由外层事务控制的
     * 内层事务异常回滚会回滚到savepoint，不影响外层事务（需要try catch包住内层事务的函数)
     * 外层事务回滚，内层事务也会回滚
     */
    @RequestMapping("/nestNest")
    public String nestNest() throws Exception {
        productInfoService.serviceE();
        return "done";
    }
}

